﻿//
// System.Net.HttpUtility
//
// Author:
//	Gonzalo Paniagua Javier (gonzalo@novell.com)
//
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

using System;
using System.Globalization;
using System.IO;
using System.Text;

namespace Tavis.Http {
    public sealed class HttpUtility {
        private HttpUtility() {
        }

        public static string UrlDecode(string s) {
            return UrlDecode(s, null);
        }

        static char[] GetChars(MemoryStream b, Encoding e) {
            return e.GetChars(b.GetBuffer(), 0, (int)b.Length);
        }

        public static string UrlDecode(string s, Encoding e) {
            if (null == s)
                return null;

            if (s.IndexOf('%') == -1 && s.IndexOf('+') == -1)
                return s;

            if (e == null)
                e = Encoding.GetEncoding(28591);

            StringBuilder output = new StringBuilder();
            long len = s.Length;
            NumberStyles hexa = NumberStyles.HexNumber;
            MemoryStream bytes = new MemoryStream();

            for (int i = 0; i < len; i++) {
                if (s[i] == '%' && i + 2 < len) {
                    if (s[i + 1] == 'u' && i + 5 < len) {
                        if (bytes.Length > 0) {
                            output.Append(GetChars(bytes, e));
                            bytes.SetLength(0);
                        }
                        output.Append((char)Int32.Parse(s.Substring(i + 2, 4), hexa));
                        i += 5;
                    } else {
                        bytes.WriteByte((byte)Int32.Parse(s.Substring(i + 1, 2), hexa));
                        i += 2;
                    }
                    continue;
                }

                if (bytes.Length > 0) {
                    output.Append(GetChars(bytes, e));
                    bytes.SetLength(0);
                }

                if (s[i] == '+') {
                    output.Append(' ');
                } else {
                    output.Append(s[i]);
                }
            }

            if (bytes.Length > 0) {
                output.Append(GetChars(bytes, e));
            }

            bytes = null;
            return output.ToString();
        }

        public static string UrlEncode(string str) {
            return UrlEncode(str, Encoding.UTF8);
        }

        public static string UrlEncode(string s, Encoding Enc) {
            if (s == null)
                return null;

            if (s == "")
                return "";

            byte[] bytes = Enc.GetBytes(s);
            return Encoding.ASCII.GetString(UrlEncodeToBytes(bytes, 0, bytes.Length));
        }

        public static string UrlEncode(byte[] bytes) {
            if (bytes == null)
                return null;

            if (bytes.Length == 0)
                return "";

            return Encoding.ASCII.GetString(UrlEncodeToBytes(bytes, 0, bytes.Length));
        }

        public static string UrlEncode(byte[] bytes, int offset, int count) {
            if (bytes == null)
                return null;

            if (bytes.Length == 0)
                return "";

            return Encoding.ASCII.GetString(UrlEncodeToBytes(bytes, offset, count));
        }

        public static byte[] UrlEncodeToBytes(string str) {
            return UrlEncodeToBytes(str, Encoding.UTF8);
        }

        public static byte[] UrlEncodeToBytes(string str, Encoding e) {
            if (str == null)
                return null;

            if (str == "")
                return new byte[0];

            byte[] bytes = e.GetBytes(str);
            return UrlEncodeToBytes(bytes, 0, bytes.Length);
        }

        public static byte[] UrlEncodeToBytes(byte[] bytes) {
            if (bytes == null)
                return null;

            if (bytes.Length == 0)
                return new byte[0];

            return UrlEncodeToBytes(bytes, 0, bytes.Length);
        }

        static char[] hexChars = "0123456789abcdef".ToCharArray();
        const string notEncoded = "!'()*-._";

        static void UrlEncodeChar(char c, Stream result, bool isUnicode) {
            if (c > 255) {
                //FIXME: what happens when there is an internal error?
                //if (!isUnicode)
                //	throw new ArgumentOutOfRangeException ("c", c, "c must be less than 256");
                int idx;
                int i = (int)c;

                result.WriteByte((byte)'%');
                result.WriteByte((byte)'u');
                idx = i >> 12;
                result.WriteByte((byte)hexChars[idx]);
                idx = (i >> 8) & 0x0F;
                result.WriteByte((byte)hexChars[idx]);
                idx = (i >> 4) & 0x0F;
                result.WriteByte((byte)hexChars[idx]);
                idx = i & 0x0F;
                result.WriteByte((byte)hexChars[idx]);
                return;
            }

            if (c > ' ' && notEncoded.IndexOf(c) != -1) {
                result.WriteByte((byte)c);
                return;
            }
            if (c == ' ') {
                result.WriteByte((byte)'+');
                return;
            }
            if ((c < '0') ||
                (c < 'A' && c > '9') ||
                (c > 'Z' && c < 'a') ||
                (c > 'z')) {
                if (isUnicode && c > 127) {
                    result.WriteByte((byte)'%');
                    result.WriteByte((byte)'u');
                    result.WriteByte((byte)'0');
                    result.WriteByte((byte)'0');
                } else
                    result.WriteByte((byte)'%');

                int idx = ((int)c) >> 4;
                result.WriteByte((byte)hexChars[idx]);
                idx = ((int)c) & 0x0F;
                result.WriteByte((byte)hexChars[idx]);
            } else
                result.WriteByte((byte)c);
        }

        public static byte[] UrlEncodeToBytes(byte[] bytes, int offset, int count) {
            if (bytes == null)
                return null;

            int len = bytes.Length;
            if (len == 0)
                return new byte[0];

            if (offset < 0 || offset >= len)
                throw new ArgumentOutOfRangeException("offset");

            if (count < 0 || count > len - offset)
                throw new ArgumentOutOfRangeException("count");

            MemoryStream result = new MemoryStream(count);
            int end = offset + count;
            for (int i = offset; i < end; i++)
                UrlEncodeChar((char)bytes[i], result, false);

            return result.ToArray();
        }

        public static string UrlEncodeUnicode(string str) {
            if (str == null)
                return null;

            return Encoding.ASCII.GetString(UrlEncodeUnicodeToBytes(str));
        }

        public static byte[] UrlEncodeUnicodeToBytes(string str) {
            if (str == null)
                return null;

            if (str == "")
                return new byte[0];

            MemoryStream result = new MemoryStream(str.Length);
            foreach (char c in str) {
                UrlEncodeChar(c, result, true);
            }
            return result.ToArray();
        }
    }
}